#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <vendor_global.h>
#include "channel_task.h"
#include <settings/config_parser.h>
#include <yaml-cpp/yaml.h>
#include "protocol/xcom_handler.h"

#define GTEST_OUT std::cout
#define GTEST_ENDL std::endl

namespace data_acq {
class ChannelTaskTest : public ::testing::Test {
public:
	void SetUp() override
	{
		uvLoop_ = uv_loop_new();
		uv_timer_init(uvLoop_, &timer_);
		uv_async_init(uvLoop_, &uvAsync_,
			[](uv_async_t *handle) { uv_stop(handle->loop); });
		protocol::XComHandler::GetXComInstance()->LoadProtocol(
			VENDOR_TEST_PATH + "/test_protocol.xcom");
	}
	void TearDown() override
	{
		devicesTask_.clear();
		uv_stop(uvLoop_);
		uv_walk(
			uvLoop_,
			[](uv_handle_t *handle, void *arg) {
				if (!uv_is_closing(handle)) {
					uv_close(handle, nullptr);
				}
			},
			nullptr);
		uv_loop_delete(uvLoop_);
	}
	void GetChannelFromFile()
	{
		YAML::Node config =
			YAML::LoadFile(VENDOR_TEST_PATH + "/task_test.yaml");
		int times = config["times"].as<int32_t>();
		YAML::Node devNode = config["device"];
		for (int i = 0; i < times; i++) {
			dbms::Device_t dev;
			dev.set_id(devNode["id"].as<std::string>());
			dev.set_addr(devNode["addr"].as<std::string>());
			dev.set_name(devNode["name"].as<std::string>());
			dev.set_channelid(devNode["channel_id"].as<int32_t>());
			dev.set_protoid(devNode["proto_id"].as<std::string>());
			YAML::Node funcpointNode = config["funcpoints"];
			for (const auto &node : funcpointNode) {
				::dbms::Funcpoint_t *funcpoint = dev.add_funclist();
				funcpoint->set_id(node["id"].as<std::string>());
				funcpoint->set_type(node["type"].as<int32_t>());
				funcpoint->set_ratio(node["ratio"].as<float>());
			}
			std::shared_ptr<DeviceTask> devTask =
				std::make_shared<DeviceTask>(dev);
			devicesTask_.push_back(devTask);
		}

		YAML::Node channelNode = config["channel"];
		deviceChannel_.set_id(channelNode["id"].as<int32_t>());
		deviceChannel_.set_timeout(channelNode["waitTime"].as<int32_t>());
		deviceChannel_.set_interval(channelNode["intervalTime"].as<int32_t>());
		deviceChannel_.set_reportperiod(
			channelNode["reportPeriod"].as<int32_t>());

		std::string type = channelNode["type"].as<std::string>();
		if (type == "Serial") {
			deviceChannel_.set_ctype(dbms::CT_COM);
			::dbms::Serial_t *com = deviceChannel_.mutable_com();
			YAML::Node params = channelNode["params"];
			settings::ConfigParser configParser;
			settings::ConfigParser::Channel chn =
				configParser.FindChannel(params["name"].as<std::string>());
			HTELINK_LOG_DEBUG(
				"%s -> %s", channelNode["name"].as<std::string>(), chn.devPath);
			com->set_name(chn.devPath);
			com->set_baudrate(params["baudRate"].as<int32_t>());
			com->set_databit(params["dataBits"].as<int32_t>());
			com->set_stopbit(params["stopBits"].as<int32_t>());
			com->set_parity(params["parity"].as<int32_t>());
		} else {
			deviceChannel_.set_ctype(dbms::CT_TCP);
			YAML::Node params = channelNode["params"];
			::dbms::Socket_t *socket = deviceChannel_.mutable_socket();
			socket->set_ip(params["ip"].as<std::string>());
			socket->set_port(params["port"].as<int32_t>());
		}
	}
	void SetupTcpServer(int32_t port)
	{
		uv_tcp_init(uvLoop_, &tcpServer_);

		struct sockaddr_in addr;
		uv_ip4_addr("0.0.0.0", port, &addr);
		uv_tcp_bind(&tcpServer_, (const struct sockaddr *)&addr, 0);

		if (uv_listen((uv_stream_t *)&tcpServer_, 128,
				[](uv_stream_t *server, int status) {
					if (status < 0) {
						fprintf(stderr, "Connection error: %s\n",
							uv_strerror(status));
						return;
					}

					// 初始化客户端句柄
					uv_tcp_t *client = (uv_tcp_t *)malloc(sizeof(uv_tcp_t));
					uv_tcp_init(server->loop, client);

					// 接受连接
					if (uv_accept(server, (uv_stream_t *)client) == 0) {
						printf("New client connected\n");
						uv_read_start((uv_stream_t *)client,
							[](uv_handle_t *handle, size_t suggested_size,
								uv_buf_t *buf) {
								buf->base = (char *)malloc(
									suggested_size); // 动态分配内存[2,6](@ref)
								buf->len = suggested_size;
							},
							[](uv_stream_t *client, ssize_t nread,
								const uv_buf_t *buf) {
								if (nread > 0) {
									printf("Received %zd bytes: %.*s\n", nread,
										(int)nread, buf->base);
									// 示例：原样回显数据
									uv_write_t *req = (uv_write_t *)malloc(
										sizeof(uv_write_t));
									uv_buf_t wrbuf =
										uv_buf_init(buf->base, nread);
									uv_write(req, client, &wrbuf, 1,
										[](uv_write_t *req, int status) {
											printf("status: %d\n", status);
											// free(req);
										});
								} else if (nread < 0) {
									if (nread != UV_EOF) {
										fprintf(stderr, "Read error: %s\n",
											uv_strerror(nread));
									}
									uv_close((uv_handle_t *)client,
										[](uv_handle_t *handle) {
											free(handle);
										});
								}
								free(buf->base); // 释放缓冲区内存[2,6](@ref)
							});
					} else {
						uv_close((uv_handle_t *)client, NULL);
					}
				})) {
			fprintf(stderr, "Listen error\n");
			return;
		}
	}
	std::vector<std::shared_ptr<DeviceTask> > devicesTask_;
	dbms::DeviceChannel_t deviceChannel_;
	uv_loop_t *uvLoop_;
	uv_tcp_t tcpServer_;
	uv_timer_t timer_;
	uv_async_t uvAsync_;
};

class MockChannelTask : public ChannelTask {
public:
	MockChannelTask(const dbms::DeviceChannel_t &channel)
		: ChannelTask(channel)
	{
	}
	MOCK_METHOD(void, OnGetDevices,
		(std::vector<std::shared_ptr<DeviceTask> > &), (override));
};

TEST_F(ChannelTaskTest, Start)
{
	GetChannelFromFile();
	if (deviceChannel_.ctype() == dbms::CT_TCP) {
		SetupTcpServer(deviceChannel_.socket().port());
	}
	auto task = std::make_shared<MockChannelTask>(deviceChannel_);
	task->Subscribe(
		[&](const dbms::Device_t &device,
			const dbms::AcqData_t &result) -> bool { return true; });
	task->SubscribeException(
		[&](const dbms::Device_t &, const std::string &) {});
	EXPECT_CALL(*task, OnGetDevices(testing::_))
		.Times(testing::AtLeast(1))
		.WillRepeatedly(testing::DoAll(
			testing::SetArgReferee<0>(devicesTask_), testing::Return()));

	std::thread threadTest([&]() {
		int times = 5;
		while (times--) {
			task->Start();
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
			EXPECT_TRUE(task->IsRunning());
			task->Stop();
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));
			EXPECT_FALSE(task->IsRunning());
		}
		uv_async_send(&uvAsync_);
		uv_walk(
			uvLoop_,
			[](uv_handle_t *handle, void *arg) {
				printf("handle type:%d\n", handle->type);
				if (!uv_is_closing(handle)) {
					uv_close(handle, nullptr);
				}
			},
			nullptr);
	});
	threadTest.detach();
	printf("start uv run\n");

	uv_timer_start(&timer_, [](uv_timer_t *handle) {}, 1000, 0);
	uv_run(uvLoop_, UV_RUN_DEFAULT);
	printf("uv exit\n");
}

TEST_F(ChannelTaskTest, Run)
{
	GetChannelFromFile();
	printf("Get Channel\n");
	if (deviceChannel_.ctype() == dbms::CT_TCP) {
		SetupTcpServer(deviceChannel_.socket().port());
	}
	printf("allock Mock Task\n");
	auto task = std::make_shared<MockChannelTask>(deviceChannel_);
	printf("subscribe\n");
	task->Subscribe(
		[&](const dbms::Device_t &device,
			const dbms::AcqData_t &result) -> bool { return true; });
	task->SubscribeException(
		[&](const dbms::Device_t &, const std::string &) {});
	printf("Mock Method\n");
	EXPECT_CALL(*task, OnGetDevices(testing::_))
		.Times(testing::AtLeast(1))
		.WillRepeatedly(testing::DoAll(
			testing::SetArgReferee<0>(devicesTask_), testing::Return()));

	std::thread threadTest([&]() {
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
		EXPECT_TRUE(task->IsRunning());
		std::this_thread::sleep_for(std::chrono::milliseconds(50000));
		task->Stop();
		uv_async_send(&uvAsync_);
		uv_walk(
			uvLoop_,
			[](uv_handle_t *handle, void *arg) {
				printf("handle type:%d\n", handle->type);
				if (!uv_is_closing(handle)) {
					uv_close(handle, nullptr);
				}
			},
			nullptr);
	});
	threadTest.detach();
	printf("Start\n");
	EXPECT_FALSE(task->IsRunning());
	task->Start();

	printf("start uv run\n");
	uv_timer_start(&timer_, [](uv_timer_t *handle) {}, 1000, 0);
	uv_run(uvLoop_, UV_RUN_DEFAULT);
	printf("uv exit\n");
}
} // namespace data_acq


int main(int argc, char *argv[])
{
	utils::set_enable(true);
	::testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}